// ========================================================================
// Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================
/**
 * Copyright 2011 Ivan Hristov
 *
 * hristov.iv [AT] gmail [DOT] com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 */
package com.google.code.johanna.scala.websocket.client

import java.io.IOException

import org.eclipse.jetty.io.Buffer
import org.eclipse.jetty.io.EndPoint
import org.eclipse.jetty.util.log.Log



/* ------------------------------------------------------------ */
/**
 * Parser the WebSocket protocol.
 *
 */

sealed case class State(needs : Int) {
  
}

case object START extends State(0);
case object OPCODE extends State(1)
case object LENGTH_7 extends State(1)
case object LENGTH_16 extends State(2)
case object LENGTH_63 extends State(8)
case object MASK extends State(4)
case object PAYLOAD extends State(0)
case object DATA extends State(0)
case object SKIP extends State(1)
        
/**
 *
 * @author Ivan Hristov
 *
 */
class WebSocketParserD7_9 extends WebSocketParser {    
 
	val CLOSE_PROTOCOL : Int=1002;
	val CLOSE_LARGE : Int=1004;
	
	var  _buffers : WebSocketBuffers = null;
    var _endp : EndPoint = null;
    var _handler : FrameHandler = null;
    var _shouldBeMasked : Boolean = false;
    var _state:State = null;
    var _buffer : Buffer = null;
    var _flags : Byte =0;
    var _opcode : Byte =0;
    var _bytesNeeded : Int = 0;
    var _length : Long = 0L;
    var _masked : Boolean = false;
    val _mask : Array[Byte]= new Array[Byte](4);
    var _m : Int = 0;
    var _skip : Boolean = false;    

    /* ------------------------------------------------------------ */
    /**
     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
     * is mostly used.
     * @param endp
     * @param handler
     */
    def this(buffers : WebSocketBuffers, endp : EndPoint, handler : FrameHandler, shouldBeMasked : Boolean){
      this()
        _buffers=buffers;
        _endp=endp;
        _handler=handler;
        _shouldBeMasked=shouldBeMasked;
        _state=START;
    }

    /* ------------------------------------------------------------ */
    def isBufferEmpty() :Boolean ={
        _buffer==null || _buffer.length()==0;
    }


    /* ------------------------------------------------------------ */
    /** Parse to next event.
     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
     * available. Fill data from the {@link EndPoint} only as necessary.
     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
     * that no bytes were read and no messages parsed. A positive number indicates either
     * the bytes filled or the messages parsed.
     */
    def parseNext() : Int = {

        if (_buffer==null)
            _buffer=_buffers.getBuffer();

        var total_filled : Int=0;
        var events :Int=0;

        // Loop until a datagram call back or can't fill anymore
        while(true) {
          
            var available : Int=_buffer.length();

            // Fill buffer if we need a byte or need length
            while (available<(if(_state==SKIP ) 1  else _bytesNeeded))
            {
                // compact to mark (set at start of data)
                _buffer.compact();

                // if no space, then the data is too big for buffer
                if (_buffer.space() == 0)
                    throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());

                // catch IOExceptions (probably EOF) and try to parse what we have
                try
                {
                    val filled : Int = if(_endp.isOpen()) _endp.fill(_buffer) else -1;
                    if (filled<=0)
                        return if((total_filled+events)>0 ) (total_filled+events) else filled;
                    total_filled+=filled;
                    available=_buffer.length();
                }
                catch {
                  case e : IOException =>
                    Log.debug(e);
                    return if((total_filled+events)>0) (total_filled+events) else -1;
                }
            }

            // if we are here, then we have sufficient bytes to process the current state.
  
            // Parse the buffer byte by byte (unless it is STATE_DATA)
            var b : Byte = 0;
            while (_state!=DATA && available>=(if(_state==SKIP) 1 else _bytesNeeded)) {
                _state match {
                    case START => 
                        _skip=false;
                        _state=OPCODE;
                        _bytesNeeded=_state.needs
                        
                    case OPCODE =>
                        b=_buffer.get();
                        available -=1
                        _opcode=(b&0xf).toByte
                        _flags=(0xf&(b>>4)).toByte
                        
                        if (isControlFrame(_opcode)&& (!isLastFrame(_flags)))
                        {
                            events+=1
                            Log.warn("Fragmented Control from "+_endp);
                            _handler.close(CLOSE_PROTOCOL,"Fragmented control");
                            _skip=true;
                        }

                        _state=LENGTH_7;
                        _bytesNeeded=_state.needs

                    case LENGTH_7 => 
                        b=_buffer.get();
                        available-=1
                        _masked= ((b&0x80)!=0);
                        b=(0x7f&b).toByte;
                        
                        b match {
                            case 0x7f =>
                                _length=0;
                                _state=LENGTH_63;
                            case 0x7e =>
                                _length=0;
                                _state=LENGTH_16;
                            case _ =>
                                _length=(0x7f&b);
                                _state= if(_masked) MASK else PAYLOAD; 
                        }
                        _bytesNeeded=_state.needs

                    case LENGTH_16 =>
                        b=_buffer.get();
                        available-=1
                        _length = _length*0x100 + (0xff&b);
                        _bytesNeeded-=1
                        if (_bytesNeeded == 0)
                        {
                            if (_length>_buffer.capacity())
                            {
                                events+=1
                                _handler.close(CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
                                _skip=true;
                            }

                            _state=if(_masked) MASK else PAYLOAD; 
                            _bytesNeeded=_state.needs
                        }

                    case LENGTH_63 =>
                        b=_buffer.get();
                        available-=1;
                        _length = _length*0x100 + (0xff&b);
                        _bytesNeeded -=1
                        if (_bytesNeeded==0){
                            _bytesNeeded=_length.toInt;
                            if (_length>=_buffer.capacity())
                            {
                                events+=1;
                                _handler.close(CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
                                _skip=true;
                            }

                            _state=if(_masked) MASK else PAYLOAD; 
                            _bytesNeeded=_state.needs
                        }

                    case MASK =>
                        _buffer.get(_mask,0,4);
                        _m=0;
                        available-=4;
                        _state=PAYLOAD;
                        _bytesNeeded=_state.needs

                    case PAYLOAD =>
                        _bytesNeeded=_length.toInt;
                        _state=if(_skip) SKIP else DATA;
                        
                    case DATA =>
                    	//Do nothing
                    case SKIP=>
                        val skip : Int =Math.min(available,_bytesNeeded);
                        _buffer.skip(skip);
                        available-=skip;
                        _bytesNeeded-=skip;
                        if (_bytesNeeded==0)
                            _state=START;
                        
                }
            }

            if (_state==DATA && available>=_bytesNeeded){
                if ( _masked!=_shouldBeMasked)
                {
                    _buffer.skip(_bytesNeeded);
                    _state=START;
                    events+=1
                    _handler.close(CLOSE_PROTOCOL,"bad mask");
                }
                else
                {
                    var data : Buffer =_buffer.get(_bytesNeeded);
                    if (_masked){
                        if (data.array()==null)
                            data=_buffer.asMutableBuffer();
                        val array : Array[Byte]= data.array();
                        val end : Int=data.putIndex();
                        for (i <- data.getIndex() until end) {
                            _m+=1
                            var index = _m%4
                            var maskByte = _mask(index).toByte
                            array(i)= (array(i)^maskByte).toByte;
                        }
                    }

                    // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
                    events+=1;
                    _handler.onFrame(_flags, _opcode, data);
                    _bytesNeeded=0;
                    _state=START;
                }

                if (_buffer.length()==0)
                {
                    _buffers.returnBuffer(_buffer);
                    _buffer=null;
                }

                return total_filled+events;
            }
        }
        throw new IllegalStateException("Method should not reach this point!")
    }

    def isControlFrame(opcode : Byte) : Boolean = {
        return (opcode&0x8)!=0;
    }
    
    def isLastFrame(flags : Byte) : Boolean = {
        return (flags&0x8)!=0;
    }
    
    def getBuffer() : Buffer ={
        return _buffer;
    }
    
    /* ------------------------------------------------------------ */
    def fill(buffer : Buffer)
    {
        if (buffer!=null && buffer.length()>0)
        {
            if (_buffer==null)
                _buffer=_buffers.getBuffer();
            _buffer.put(buffer);
            buffer.clear();
        }
    }

}
